ADVERTISEMENT
Responsive Ad Unit • Modern C++ Toolchains & Compilers
🎯 Core Learning Objectives
  • Master ISO C++ standards, compiler mechanics, and idioms (C++11/14/17/20).
  • Implement memory-safe patterns, RAII, dynamic polymorphism, and generic templates.
  • Analyze assembly output, cache efficiency, and move semantics.
  • Demonstrate conceptual mastery via the complete 12-question technical assessment.

In C++ programming, templates allow for the definition of generic classes and functions that work with any data type. They enable a form of code reusability and type independence, where the same piece of code can operate on different types without needing to write the same code multiple times for each type.

5.1. Templates

5.1.1. Types of Templates

  • Function Templates
  • Class Templates

1. Function Templates

A function template is a blueprint for creating a function that can work with any data type. Instead of defining a function for each data type, you write a function template, and the compiler generates the correct function for the specific data type when you call it.

Syntax of Function Templates:

C++ Source
template <typename T>  // 'T' is a placeholder for a data type
return_type function_name(parameters) {
    // function body
}

Example:

Let's create a simple function template that finds the maximum of two values.

C++ Source
#include <iostream>
using namespace std;

// Function template to find the maximum of two values
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << "Max of 5 and 10: " << max(5, 10) << endl;          // Integer
    cout << "Max of 3.5 and 2.5: " << max(3.5, 2.5) << endl;    // Float
    cout << "Max of 'a' and 'z': " << max('a', 'z') << endl;    // Char
    return 0;
}

Explanation:

  • The max() function template works with any data type that supports the > operator.
  • In the main() function, we call max() with different types: int, float, and char. The compiler automatically generates the appropriate version of the max function for each type.

Output:

C++ Source
Max of 5 and 10: 10
Max of 3.5 and 2.5: 3.5
Max of 'a' and 'z': z

2. Class Templates

A class template is a blueprint for creating a class that can operate with any data type. It allows you to create generic classes and objects that work with any data type.

Syntax of Class Templates:

C++ Source
template <typename T>  // 'T' is a placeholder for a data type
class ClassName {
public:
    T member_variable; // A member variable of type T

    ClassName(T value) {  // Constructor accepting a value of type T
        member_variable = value;
    }

    T get_value() {
        return member_variable;
    }
};

Example:

Let's create a Box class template that holds a value of any type.

C++ Source
#include <iostream>
using namespace std;

// Class template for Box
template <typename T>
class Box {
private:
    T value; // A member variable of type T

public:
    // Constructor to initialize value
    Box(T val) {
        value = val;
    }

    // Method to get the value
    T get_value() {
        return value;
    }
};

int main() {
    // Creating objects of Box with different types
    Box<int> intBox(10);
    Box<double> doubleBox(3.14);
    Box<string> stringBox("Hello, Templates!");

    cout << "Integer Box Value: " << intBox.get_value() << endl;
    cout << "Double Box Value: " << doubleBox.get_value() << endl;
    cout << "String Box Value: " << stringBox.get_value() << endl;

    return 0;
}

Explanation:

  • The Box class template is defined to hold a value of any data type T.
  • In the main() function, we create Box objects with different types: int, double, and string.
  • The get_value() method retrieves the stored value for each type.

Output:

C++ Source
Integer Box Value: 10
Double Box Value: 3.14
String Box Value: Hello, Templates!

5.1.2. Template Specialization

Sometimes, you may want to provide a custom implementation of a template for a specific data type. This is where template specialization comes in. It allows you to define a specific version of a template for a given data type.

Example of Template Specialization:

C++ Source
#include <iostream>
using namespace std;

// Generic template
template <typename T>
void print(T value) {
    cout << "Generic: " << value << endl;
}

// Template specialization for int
template <>
void print<int>(int value) {
    cout << "Specialized for int: " << value << endl;
}

int main() {
    print(10);         // Calls specialized version for int
    print(3.14);       // Calls generic version
    print("Hello");    // Calls generic version

    return 0;
}

Explanation:

  • We have a generic print() function template.
  • We provide a specialized version of print() for int, which has custom behavior.

Output:

C++ Source
Specialized for int: 10
Generic: 3.14
Generic: Hello

Template Parameters

Templates can also accept multiple parameters, and they can be either type parameters or non-type parameters (like integer values).

Multiple Type Parameters Example:

C++ Source
template <typename T1, typename T2>
void print_pair(T1 a, T2 b) {
    cout << "First: " << a << ", Second: " << b << endl;
}

int main() {
    print_pair(10, 3.14);         // Integer and float
    print_pair("Hello", 42);      // String and integer

    return 0;
}

Non-Type Template Parameters Example:

C++ Source
template <typename T, int size>
class Array {
private:
    T arr[size];

public:
    void set(int index, T value) {
        arr[index] = value;
    }

    T get(int index) {
        return arr[index];
    }
};

int main() {
    Array<int, 5> arr1;  // Array of 5 integers
    arr1.set(0, 10);
    cout << "First element: " << arr1.get(0) << endl;

    return 0;
}

Explanation:

  • In the second example, the size of the array is passed as a non-type template parameter (int size).

Benefits of Templates

  • Code Reusability: Templates allow you to write generic code that works with any data type.
  • Type Safety: The compiler checks types at compile time, so there are fewer runtime errors compared to using void* or casting.
  • Efficiency: Templates often result in more efficient code because the compiler generates optimized code for each type at compile time.

Conclusion

Templates in C++ are a powerful feature that provides generic programming capabilities. They enable the creation of functions and classes that can operate on any data type. Understanding templates and their application is essential for writing efficient and reusable code in C++.

5.2. Standard Template Library (STL)

The Standard Template Library (STL) in C++ is a powerful set of C++ template classes to provide general-purpose, reusable implementations of many popular algorithms and data structures. The STL provides a collection of algorithms, containers, iterators, and function objects. It is one of the core components of the C++ Standard Library and is widely used in C++ programming due to its efficiency and flexibility.

STL is based on the following components:

  • Containers: These are data structures that store collections of objects or data. Some common container types include arrays, lists, vectors, sets, maps, etc.
  • Algorithms: A set of predefined operations that can be performed on data stored in containers. Examples include sorting, searching, and modifying elements.
  • Iterators: These are objects used to point to elements within containers. Iterators act as a generalized pointer to traverse through the contents of containers.
  • Function Objects: These are objects or classes that can be used as arguments to algorithms, or they can define specific operations for algorithms. Function objects are instances of classes that overload the operator().

Key Components of STL

1. Containers

Containers store data. STL provides several types of containers based on the required functionality and performance characteristics:

  • Sequence Containers: These maintain the ordering of elements.
    • vector: A dynamic array, allows random access and is efficient for appending elements.
    • deque: A double-ended queue, allows efficient insertions and deletions at both ends.
    • list: A doubly-linked list, allows efficient insertions and deletions at any point.
    • array: A fixed-size array, introduced in C++11.
  • Associative Containers: These store key-value pairs and allow fast lookups based on keys.
    • set: A collection of unique elements, sorted automatically.
    • map: A collection of key-value pairs, where each key is unique.
    • multiset: A collection of elements that can have duplicate values.
    • multimap: A collection of key-value pairs where multiple identical keys are allowed.
  • Unordered Containers: These use hash tables to store elements, offering faster average time complexity for lookups.
    • unordered_set: A set with hash-based storage.
    • unordered_map: A map with hash-based storage.
    • unordered_multiset: A multiset with hash-based storage.
    • unordered_multimap: A multimap with hash-based storage.

2. Algorithms

STL provides a wide range of generic algorithms that can be used on containers and data types. Common operations include:

  • Searching: find(), binary_search()
  • Sorting: sort(), stable_sort()
  • Modifying: reverse(), transform()
  • Removing: remove(), remove_if()
  • Operations on ranges: accumulate(), for_each()

Example of sorting using an algorithm:

C++ Source
#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> v = {5, 2, 9, 1, 5, 6};

    std::sort(v.begin(), v.end());

    for(int n : v) {
        std::cout << n << " ";
    }

    return 0;
}

Output:

C++ Source
1 2 5 5 6 9

3. Iterators

Iterators are used to access elements of containers. They allow you to loop through container elements in a standard way, abstracting away the details of container types.

  • begin() and end() return iterators pointing to the first and one past the last element, respectively.
  • ++ and -- allow moving forward and backward through the container.
  • * allows accessing the element pointed to by the iterator.

Example:

C++ Source
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {10, 20, 30, 40};

    // Using an iterator to traverse the vector
    for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << " ";
    }

    return 0;
}

Output:

C++ Source
10 20 30 40

4. Function Objects (Functors)

A function object (or functor) is any object that can be called as if it were a function. Functors are useful for customizing algorithms and using them with STL containers.

A basic example of a functor:

C++ Source
#include <iostream>
#include <algorithm>
#include <vector>

class Square {
public:
    void operator()(int &n) {
        n *= n;
    }
};

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // Apply square function object to each element
    std::for_each(v.begin(), v.end(), Square());
    
    for (int n : v) {
        std::cout << n << " ";
    }
    
    return 0;
}

Output:

C++ Source
1 4 9 16 25

Examples of STL Usage

Example 1: Using a Vector

Vectors are dynamic arrays that automatically resize when elements are added or removed.

C++ Source
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;

    // Inserting elements at the end
    vec.push_back(10);
    vec.push_back(20);
    vec.push_back(30);

    // Accessing elements
    std::cout << "First element: " << vec[0] << std::endl;
    std::cout << "Last element: " << vec.back() << std::endl;

    // Iterating through the vector
    for(int i : vec) {
        std::cout << i << " ";
    }

    return 0;
}

Output:

C++ Source
First element: 10
Last element: 30
10 20 30

Example 2: Using a Map

Maps store key-value pairs and maintain the order of keys.

C++ Source
#include <iostream>
#include <map>

int main() {
    std::map<int, std::string> myMap;

    // Inserting elements
    myMap[1] = "One";
    myMap[2] = "Two";
    myMap[3] = "Three";

    // Accessing elements using key
    std::cout << "Element with key 2: " << myMap[2] << std::endl;

    // Iterating through the map
    for (const auto& pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

Output:

C++ Source
Element with key 2: Two
1: One
2: Two
3: Three

Summary

The Standard Template Library (STL) in C++ is a versatile and powerful library that provides generic algorithms, data structures, and utilities. Key features of STL include:

  • Containers for storing data.
  • Algorithms to operate on data.
  • Iterators to traverse containers.
  • Function objects for customizing algorithms.

STL helps you to write efficient and reusable code, leveraging the power of generic programming. With its rich set of features, STL simplifies complex programming tasks, reduces errors, and improves code readability and maintainability.

sequence containers

In C++ programming, sequence containers are a type of container from the Standard Template Library (STL) that store collections of elements in a linear sequence. The key sequence containers in C++ are:

  • vector
  • deque
  • list
  • array

Each of these containers has distinct characteristics in terms of how they manage elements, access them, and how efficient operations on them are.

1. vector

A vector is a dynamic array that can grow in size during runtime. It is a sequence container that stores elements contiguously in memory, similar to an array. Vectors provide fast random access to elements but can be inefficient when inserting or deleting elements in the middle of the sequence.

Key Characteristics:

  • Dynamic resizing: A vector can grow as needed.
  • Random access: Elements can be accessed using indices in constant time O(1).
  • Efficient at the back: Adding/removing elements at the end is efficient O(1) on average.
  • Inefficient at the front or middle: Insertion/deletion at the front or middle is O(n).

Example:

C++ Source
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};
    
    // Adding elements
    v.push_back(6);
    
    // Accessing elements
    std::cout << "First element: " << v[0] << std::endl;
    
    // Iterating over vector
    std::cout << "All elements: ";
    for (int val : v) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // Removing last element
    v.pop_back();

    return 0;
}

Output:

C++ Source
First element: 1
All elements: 1 2 3 4 5 6

2. deque (Double-ended queue)

A deque is a sequence container that allows efficient insertion and deletion of elements from both the front and back. It is implemented as a dynamic array of smaller fixed-size arrays. Unlike vectors, deques are optimized for adding or removing elements at both ends.

Key Characteristics:

  • Fast insertion and deletion at both ends: O(1).
  • Random access: Like vectors, deques allow random access in O(1).
  • The elements are not stored contiguously, unlike vectors.

Example:

C++ Source
#include <iostream>
#include <deque>

int main() {
    std::deque<int> dq = {1, 2, 3, 4, 5};
    
    // Inserting at the front
    dq.push_front(0);
    
    // Inserting at the back
    dq.push_back(6);
    
    // Accessing elements
    std::cout << "First element: " << dq.front() << std::endl;
    std::cout << "Last element: " << dq.back() << std::endl;

    // Iterating over deque
    std::cout << "All elements: ";
    for (int val : dq) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

Output:

C++ Source
First element: 0
Last element: 6
All elements: 0 1 2 3 4 5 6

3. list

A list is a doubly linked list where each element points to both its previous and next element. This allows fast insertion and deletion from any position in the list, but it does not support efficient random access.

Key Characteristics:

  • Fast insertion and deletion at any position: O(1) for insertions or deletions after an iterator.
  • Slow random access: Accessing elements is O(n) because it requires traversing the list.
  • The elements are not stored contiguously.

Example:

C++ Source
#include <iostream>
#include <list>

int main() {
    std::list<int> lst = {1, 2, 3, 4, 5};
    
    // Inserting at the front
    lst.push_front(0);
    
    // Inserting at the back
    lst.push_back(6);
    
    // Iterating over list
    std::cout << "All elements: ";
    for (int val : lst) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    // Removing an element
    lst.remove(3);  // Removes all occurrences of 3

    std::cout << "After removal of 3: ";
    for (int val : lst) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

Output:

C++ Source
All elements: 0 1 2 3 4 5 6 
After removal of 3: 0 1 2 4 5 6

4. array

An array is a fixed-size sequence container that holds elements in contiguous memory locations. Unlike vectors, arrays cannot grow in size after being created. They are useful when you know the size of the container at compile time and require fast access.

Key Characteristics:

  • Fixed size: The size is determined at compile-time.
  • Fast random access: O(1).
  • No dynamic resizing.
  • Arrays are ideal when the number of elements is known in advance and does not change.

Example:

C++ Source
#include <iostream>
#include <array>

int main() {
    std::array<int, 5> arr = {1, 2, 3, 4, 5};
    
    // Accessing elements
    std::cout << "First element: " << arr[0] << std::endl;
    
    // Iterating over array
    std::cout << "All elements: ";
    for (int val : arr) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

Output:

C++ Source
First element: 1
All elements: 1 2 3 4 5

Comparison of Containers

Container Insert at front Insert at back Access time Dynamic size Best for
vector O(n) O(1) O(1) Yes When you need random access and efficient back insertion
deque O(1) O(1) O(1) Yes When you need efficient insertion and deletion at both ends
list O(1) O(1) O(n) Yes When you need efficient insertions/deletions at any position
array N/A N/A O(1) No When the size is known and fixed at compile time

Summary

  • vector: Best for situations where you need fast random access and occasional insertion/deletion at the end.
  • deque: Best when you need fast insertions or deletions at both ends and can tolerate a bit more overhead than a vector.
  • list: Best when frequent insertions or deletions are needed at arbitrary positions, but random access is not a priority.
  • array: Best when you know the size ahead of time and need efficient access and storage without resizing.

Each container serves a different purpose, and the choice of which to use depends on the specific requirements of your program in terms of performance and functionality.

Associative containers

In C++ programming, associative containers are a part of the Standard Template Library (STL). These containers store elements in a way that allows for fast retrieval, and they automatically maintain a certain order. The most commonly used associative containers are:

  • set
  • map
  • multiset
  • multimap

Each of these containers is implemented as a balanced binary tree (usually a Red-Black tree), which provides logarithmic time complexity (O(log n)) for operations like insertion, deletion, and search.

1. set

A set is a container that stores unique elements in a sorted order. It does not allow duplicate values. Elements in a set are automatically arranged in a specific order (usually ascending by default, but you can specify a custom sorting criterion).

Characteristics:

  • Unique elements: No duplicates are allowed.
  • Sorted: The elements are sorted by default (ascending), or you can define a custom comparator.
  • No direct access: You cannot access an element by index; you can only iterate through the set.

Example of set:

C++ Source
#include <iostream>
#include <set>

int main() {
    std::set<int> s;

    // Inserting elements into the set
    s.insert(10);
    s.insert(20);
    s.insert(15);
    s.insert(10); // Duplicate element, will not be inserted

    // Displaying elements in sorted order
    for (int x : s) {
        std::cout << x << " ";
    }

    return 0;
}

Output:

C++ Source
10 15 20

In this example, the element 10 is not inserted twice, as the set automatically handles uniqueness.

2. map

A map is a collection of key-value pairs, where each key is unique. In a map, the keys are automatically sorted (based on a comparator, usually ascending), and the values are associated with those keys. You can access the value by referencing its key.

Characteristics:

  • Unique keys: Each key is unique.
  • Key-value pairs: Each element consists of a key and a corresponding value.
  • Sorted keys: The keys are automatically sorted.

Example of map:

C++ Source
#include <iostream>
#include <map>

int main() {
    std::map<int, std::string> m;

    // Inserting key-value pairs into the map
    m[1] = "apple";
    m[2] = "banana";
    m[3] = "cherry";
    m[2] = "orange"; // Update value for key 2

    // Displaying key-value pairs
    for (const auto& pair : m) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

Output:

C++ Source
1: apple
2: orange
3: cherry

In this example, when the key 2 is inserted a second time, its value is updated to "orange". The map does not allow duplicate keys.

3. multiset

A multiset is similar to a set, but unlike a set, it allows duplicate elements. Like a set, the elements in a multiset are stored in a sorted order.

Characteristics:

  • Duplicates allowed: Unlike a set, a multiset allows the insertion of duplicate elements.
  • Sorted: The elements are sorted automatically.
  • No direct access: You cannot access an element by index; you can only iterate over it.

Example of multiset:

C++ Source
#include <iostream>
#include <set>

int main() {
    std::multiset<int> ms;

    // Inserting elements into the multiset
    ms.insert(10);
    ms.insert(20);
    ms.insert(10);
    ms.insert(15);

    // Displaying elements in sorted order
    for (int x : ms) {
        std::cout << x << " ";
    }

    return 0;
}

Output:

C++ Source
10 10 15 20

In this example, the number 10 appears twice because the multiset allows duplicates.

4. multimap

A multimap is similar to a map, but it allows duplicate keys. A multimap stores key-value pairs where the keys are not unique, and multiple values can be associated with the same key.

Characteristics:

  • Duplicate keys allowed: Unlike a map, a multimap allows multiple elements with the same key.
  • Key-value pairs: Each element consists of a key and a corresponding value.
  • Sorted keys: The keys are automatically sorted.

Example of multimap:

C++ Source
#include <iostream>
#include <map>

int main() {
    std::multimap<int, std::string> mm;

    // Inserting key-value pairs into the multimap
    mm.insert({1, "apple"});
    mm.insert({1, "banana"});
    mm.insert({2, "cherry"});
    mm.insert({1, "orange"});

    // Displaying key-value pairs
    for (const auto& pair : mm) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

Output:

C++ Source
1: apple
1: banana
1: orange
2: cherry

In this example, the key 1 is repeated with different values ("apple", "banana", and "orange"), which is allowed in a multimap.

Summary of Differences

Container Allows Duplicates? Stores Pairs? Sorted? Access Method
set No No Yes Iteration only
map No Yes Yes Access by key
multiset Yes No Yes Iteration only
multimap Yes Yes Yes Access by key

When to Use Each:

  • set: When you need to store unique elements and need them sorted (e.g., a collection of unique IDs).
  • map: When you need to associate unique keys with values and need fast lookup by key (e.g., a dictionary).
  • multiset: When you need to store multiple occurrences of elements, but want them sorted (e.g., frequency count of elements).
  • multimap: When you need to associate multiple values with the same key (e.g., phone numbers associated with a name).

These containers provide fast and efficient ways to manage data with automatic sorting and handling of uniqueness or duplicates.

Unordered containers

Unordered containers in C++ are part of the Standard Template Library (STL) and provide a way to store data that doesn't require ordering. These containers are implemented using hash tables, which allows for faster average time complexity for operations like insertion, deletion, and searching, compared to their ordered counterparts such as set and map.

The unordered containers are:

  • unordered_set
  • unordered_map
  • unordered_multiset
  • unordered_multimap

These containers are part of the <unordered_map> and <unordered_set> header files.

1. unordered_set

unordered_set is a container that stores unique elements in no particular order. The key difference between unordered_set and set is that unordered_set does not maintain any specific order of elements, whereas set sorts elements automatically.

Key Characteristics:

  • Stores unique elements.
  • Uses hashing for storage (elements are placed in buckets).
  • Operations like insert, find, and erase generally have average constant time complexity, i.e., O(1).

Example of unordered_set:

C++ Source
#include <iostream>
#include <unordered_set>
using namespace std;

int main() {
    unordered_set<int> uset;

    // Inserting elements
    uset.insert(10);
    uset.insert(20);
    uset.insert(30);
    uset.insert(20); // Duplicate element (won't be inserted)

    // Displaying elements
    for (auto elem : uset) {
        cout << elem << " ";
    }
    cout << endl;

    // Check if an element is present
    if (uset.find(20) != uset.end()) {
        cout << "20 found!" << endl;
    } else {
        cout << "20 not found!" << endl;
    }

    return 0;
}

Output:

C++ Source
10 20 30 
20 found!

2. unordered_map

unordered_map is a container that stores key-value pairs, similar to map. However, unlike map, which keeps elements sorted by their keys, unordered_map does not maintain any order. The keys are hashed to determine the storage location.

Key Characteristics:

  • Stores unique key-value pairs.
  • Keys are hashed and stored in buckets.
  • Average time complexity for insertion, deletion, and search is O(1).

Example of unordered_map:

C++ Source
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_map<string, int> umap;

    // Inserting key-value pairs
    umap["apple"] = 5;
    umap["banana"] = 3;
    umap["cherry"] = 8;
    umap["apple"] = 7; // Updating the value of "apple"

    // Displaying elements
    for (auto& elem : umap) {
        cout << elem.first << " -> " << elem.second << endl;
    }

    // Searching for a key
    if (umap.find("banana") != umap.end()) {
        cout << "Banana found with value: " << umap["banana"] << endl;
    } else {
        cout << "Banana not found!" << endl;
    }

    return 0;
}

Output:

C++ Source
banana -> 3
apple -> 7
cherry -> 8
Banana found with value: 3

3. unordered_multiset

unordered_multiset is similar to unordered_set, but it allows multiple occurrences of the same element. Like unordered_set, the elements are stored in an unordered fashion using hashing.

Key Characteristics:

  • Stores elements but allows duplicates.
  • Uses hashing to store elements.
  • Average time complexity for insertion, deletion, and search is O(1).

Example of unordered_multiset:

C++ Source
#include <iostream>
#include <unordered_set>
using namespace std;

int main() {
    unordered_multiset<int> ums;

    // Inserting elements (duplicates are allowed)
    ums.insert(10);
    ums.insert(20);
    ums.insert(30);
    ums.insert(20); // Duplicate allowed

    // Displaying elements
    for (auto elem : ums) {
        cout << elem << " ";
    }
    cout << endl;

    // Count occurrences of an element
    cout << "20 occurs " << ums.count(20) << " times" << endl;

    return 0;
}

Output:

C++ Source
10 20 30 20 
20 occurs 2 times

4. unordered_multimap

unordered_multimap is similar to unordered_map, but it allows multiple pairs of the same key with different values. It is also unordered and uses hashing for storage.

Key Characteristics:

  • Stores multiple key-value pairs, where keys can be duplicated.
  • Uses hashing for storage, like unordered_map.
  • Average time complexity for insertion, deletion, and search is O(1).

Example of unordered_multimap:

C++ Source
#include <iostream>
#include <unordered_map>
using namespace std;

int main() {
    unordered_multimap<string, int> umm;

    // Inserting key-value pairs (duplicates are allowed for keys)
    umm.insert({"apple", 5});
    umm.insert({"banana", 3});
    umm.insert({"apple", 7});
    umm.insert({"apple", 10});

    // Displaying elements
    for (auto& elem : umm) {
        cout << elem.first << " -> " << elem.second << endl;
    }

    // Count occurrences of a key
    cout << "Apple appears " << umm.count("apple") << " times" << endl;

    return 0;
}

Output:

C++ Source
banana -> 3
apple -> 5
apple -> 7
apple -> 10
Apple appears 3 times

Summary of Unordered Containers

Container Key-Value Pair Duplicate Keys Order of Elements Average Time Complexity (Insertion/Search/Erase)
unordered_set No No Unordered O(1)
unordered_map Yes No Unordered O(1)
unordered_multiset No Yes Unordered O(1)
unordered_multimap Yes Yes Unordered O(1)

Conclusion

Unordered containers in C++ are highly efficient for scenarios where order does not matter but fast insertion, deletion, and search are important. The use of hashing allows these containers to offer average constant time complexity for most operations. However, because they do not maintain any order, they may not be suitable for cases where sorted data is required.

5.3. Exception Handling

Exception Handling in C++ is a mechanism used to handle runtime errors, making the program more robust and less likely to crash due to unforeseen problems. Instead of the program failing abruptly when an error occurs, C++ provides a way to catch exceptions, report them, and take corrective actions without terminating the program.

Key Concepts of Exception Handling in C++:

  • try block – This is where you place code that might throw an exception.
  • throw statement – This is used to "throw" an exception when an error occurs.
  • catch block – This is used to "catch" and handle exceptions thrown by the try block.

Syntax:

C++ Source
try {
    // Code that may throw an exception
} 
catch (exceptionType1 e1) {
    // Handle exception of type exceptionType1
}
catch (exceptionType2 e2) {
    // Handle exception of type exceptionType2
}
...
  • throw is used inside the try block to signal an error condition.
  • catch is used to define what happens when an exception of a specific type is thrown.

Steps of Exception Handling:

  1. The code within the try block executes normally.
  2. When an exception occurs, the control is transferred to the corresponding catch block that matches the type of exception thrown.
  3. After the exception is caught and handled, the program continues executing from the point after the try-catch block.

Example 1: Basic Exception Handling

C++ Source
#include <iostream>
using namespace std;

int divide(int a, int b) {
    if (b == 0) {
        throw "Division by zero error"; // throw exception if b is zero
    }
    return a / b;
}

int main() {
    int x = 10, y = 0;
    
    try {
        cout << "Result: " << divide(x, y) << endl; // Will throw exception
    }
    catch (const char* msg) {
        cout << "Exception caught: " << msg << endl; // Handle exception
    }

    cout << "Program continues..." << endl;
    return 0;
}

Output:

C++ Source
Exception caught: Division by zero error
Program continues...

Explanation:

  • The divide() function checks if b is zero and throws an exception if so.
  • The main() function calls divide() within a try block. If b is zero, the exception is thrown and caught by the catch block.
  • The catch block prints the error message and then the program continues after the exception is handled.

Example 2: Catching Different Types of Exceptions

C++ allows you to catch different types of exceptions. You can define multiple catch blocks to handle different types of exceptions.

C++ Source
#include <iostream>
using namespace std;

void testFunction(int val) {
    if (val == 0) {
        throw "Zero error";
    } else if (val == 1) {
        throw 1;
    } else {
        cout << "Function executed successfully!" << endl;
    }
}

int main() {
    try {
        testFunction(1); // Throws integer exception
    }
    catch (const char* msg) {
        cout << "Caught string exception: " << msg << endl;
    }
    catch (int e) {
        cout << "Caught integer exception: " << e << endl;
    }
    catch (...) {
        cout << "Caught unknown exception" << endl;
    }

    return 0;
}

Output:

C++ Source
Caught integer exception: 1

Explanation:

  • In testFunction(), exceptions are thrown based on the value passed to the function.
  • In the main() function, the first exception to match the type will be caught. The second catch block catches integer exceptions (int), and the last catch block (catch (...)) catches all other types of exceptions.
  • In this case, since 1 is passed, the integer exception block catches the exception and prints the message.

Example 3: Exception Handling with Standard Exception Classes

C++ Standard Library provides a set of predefined exception classes, including std::exception, std::logic_error, std::runtime_error, etc.

C++ Source
#include <iostream>
#include <stdexcept>
using namespace std;

void testFunction(int val) {
    if (val == 0) {
        throw runtime_error("Runtime error occurred!");
    }
    else if (val == 1) {
        throw logic_error("Logic error occurred!");
    }
    cout << "No error, value: " << val << endl;
}

int main() {
    try {
        testFunction(0);  // Throws runtime_error
    }
    catch (const runtime_error& e) {
        cout << "Caught exception: " << e.what() << endl;
    }
    catch (const logic_error& e) {
        cout << "Caught exception: " << e.what() << endl;
    }

    return 0;
}

Output:

C++ Source
Caught exception: Runtime error occurred!

Explanation:

  • In this example, runtime_error and logic_error are used from the C++ Standard Library to throw exceptions.
  • The catch block catches the specific exception type and uses e.what() to print the error message.

Example 4: Exception Propagation

Exceptions can also propagate up the call stack if they are not caught immediately.

C++ Source
#include <iostream>
using namespace std;

void functionB() {
    throw "Error in functionB";  // Exception thrown here
}

void functionA() {
    functionB();  // Exception is propagated
}

int main() {
    try {
        functionA();  // FunctionA calls functionB, where exception is thrown
    }
    catch (const char* msg) {
        cout << "Caught exception: " << msg << endl;  // Exception caught here
    }
    return 0;
}

Output:

C++ Source
Caught exception: Error in functionB

Explanation:

  • An exception is thrown in functionB().
  • The exception propagates up to functionA(), and then to main().
  • The exception is caught in main(), and the error message is printed.

Best Practices for Exception Handling:

  • Use specific exceptions: Always catch specific exceptions first, and use the base exception (std::exception) as the last resort.
  • Avoid overuse: Only use exceptions for exceptional situations, not for control flow.
  • Don't ignore exceptions: Always handle exceptions properly (either by catching them or by rethrowing).
  • RAII principle: Resources should be managed using objects (e.g., classes) that automatically release resources when they go out of scope. This minimizes the need for explicit cleanup in the catch blocks.

Conclusion: C++ exception handling provides a structured way to deal with runtime errors by using try, throw, and catch blocks. It ensures that errors do not lead to program crashes and allows the program to handle them in a controlled manner. It is important to handle exceptions appropriately to avoid leaving the program in an unstable state.

5.3.3. Namespaces

A namespace in C++ is a feature that allows you to group related classes, functions, variables, and other identifiers together under a single name to avoid naming conflicts. It helps manage large codebases, especially when multiple libraries are involved, by preventing ambiguity when different libraries use the same name for different entities.

In simple terms, namespaces in C++ are used to organize code into logical groups and to prevent name conflicts.

Why Use Namespaces?

  • Avoid Name Conflicts: In a large program, different parts of the program or libraries may use the same name for variables, functions, or classes. Namespaces allow these names to coexist without conflicts.
  • Organization: It helps in grouping related functions, classes, and variables together, making the code more readable and maintainable.

Syntax of a Namespace:

C++ Source
namespace NamespaceName {
    // Declarations
    int a = 10;
    void myFunction() {
        // Function definition
    }
}

In this syntax:

  • namespace is the keyword to define the namespace.
  • NamespaceName is the name of the namespace.
  • Inside the curly braces {}, you can define variables, functions, classes, etc.

Example of Using Namespaces

Here’s a simple example of defining and using a namespace in C++:

C++ Source
#include <iostream>
using namespace std;

// Define a namespace
namespace MyNamespace {
    int a = 10;
    
    void display() {
        cout << "Value of a: " << a << endl;
    }
}

int main() {
    // Accessing members of MyNamespace
    MyNamespace::display();  // Calls display() from MyNamespace
    cout << "Value of a in MyNamespace: " << MyNamespace::a << endl;
    return 0;
}

Output:

C++ Source
Value of a: 10
Value of a in MyNamespace: 10

Accessing Namespace Members

To access members (variables, functions, etc.) of a namespace, you need to use the :: operator, followed by the member name.

Example:

C++ Source
namespace Math {
    int add(int x, int y) {
        return x + y;
    }
}

int main() {
    int result = Math::add(3, 4);  // Using Math namespace function
    cout << "Sum: " << result << endl;
    return 0;
}

Output:

C++ Source
Sum: 7

Using the using Keyword

Instead of writing NamespaceName::member, you can use the using directive to bring all or specific members of a namespace into the current scope.

Using entire namespace:

C++ Source
using namespace MyNamespace;

This will bring all the identifiers from MyNamespace into the global namespace, allowing you to access them directly without needing the MyNamespace:: prefix.

C++ Source
#include <iostream>
using namespace std;
using namespace MyNamespace;

int main() {
    display(); // No need to use MyNamespace::display
    cout << "Value of a: " << a << endl; // No need to use MyNamespace::a
    return 0;
}

Note: The using namespace directive is helpful in smaller programs, but in large projects or libraries, it is recommended to use specific using declarations to avoid confusion between similarly named identifiers from different namespaces.

Using specific members:

C++ Source
using MyNamespace::a;
using MyNamespace::display;

This allows only the specified members from the namespace to be used in the current scope.

Nested Namespaces

You can also have nested namespaces. For example:

C++ Source
namespace Outer {
    namespace Inner {
        void showMessage() {
            cout << "Hello from the inner namespace!" << endl;
        }
    }
}

int main() {
    Outer::Inner::showMessage();  // Accessing the nested namespace function
    return 0;
}

Anonymous Namespaces

An anonymous namespace is a special type of namespace with no name. It is often used to avoid name conflicts in a file-scoped context. The members of an anonymous namespace are only visible in the file they are defined in.

Example:

C++ Source
#include <iostream>

namespace {
    int a = 10; // Anonymous namespace
    void display() {
        std::cout << "Value of a: " << a << std::endl;
    }
}

int main() {
    display(); // Works fine because it is within the same file
    return 0;
}

In this case, a and display() are only accessible within this file, preventing accidental name conflicts with other parts of the program.

Namespace Aliases

Sometimes, namespaces have long names, and it can be cumbersome to type them repeatedly. You can create a namespace alias for convenience.

Example:

C++ Source
namespace LargeNamespaceName {
    void foo() {
        std::cout << "Hello from LargeNamespaceName!" << std::endl;
    }
}

namespace LN = LargeNamespaceName;  // Alias for LargeNamespaceName

int main() {
    LN::foo(); // Use the alias LN to access foo
    return 0;
}

Standard Namespace (std)

The C++ Standard Library functions, classes, and objects are all part of the std namespace. For example, cout, cin, and vector are part of the std namespace.

Example:

C++ Source
#include <iostream>
#include <vector>

int main() {
    std::vector<int> v = {1, 2, 3, 4};
    for (int i : v) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
    return 0;
}

Without the std:: prefix, you could use using namespace std;, but it is often better to avoid this in larger programs to reduce the chances of name conflicts.

Summary

  • Namespace is a feature in C++ that allows you to group related code and avoid name conflicts.
  • It is defined using the namespace keyword followed by the namespace name and enclosed within curly braces.
  • You access namespace members using the :: operator.
  • The using keyword allows you to bring namespace members into scope.
  • Anonymous namespaces and namespace aliases are advanced features that help organize and simplify code.

Namespaces are a powerful tool in C++ to structure your program and ensure that identifiers do not conflict with each other, especially when integrating multiple libraries.

ADVERTISEMENT
Responsive Ad Unit • Software Engineering & High-Performance Systems
πŸ“ Concept Mastery Assessment
12 Questions Score: 0 / 0

Select your answer for each question below to receive immediate correctness evaluation and in-depth technical rationale.

Question 1 of 12
What is operator overloading in C++?
Explanation
Operator overloading allows developers to define how built-in operators behave on user-defined classes (such as adding two Vector3D objects with '+').
Question 2 of 12
What is a 'friend' function or class in C++?
Explanation
A 'friend' declaration gives a non-member function or outside class access to private/protected members (frequently used for stream insertion operator<<).
Question 3 of 12
Which C++ standard template library (STL) container represents a dynamic, contiguous array with O(1) amortized append time?
Explanation
std::vector is C++'s default dynamic array container, storing elements contiguously with fast random access and amortized O(1) push_back.
Question 4 of 12
What is the underlying data structure typically used to implement 'std::map' in standard C++ implementations?
Explanation
std::map stores key-value pairs sorted by key in a red-black tree, guaranteeing O(log N) search, insertion, and removal.
Question 5 of 12
What is the difference between 'std::map' and 'std::unordered_map'?
Explanation
std::unordered_map uses a hash table offering O(1) average lookup time without sorting, while std::map uses a red-black tree with O(log N) sorted keys.
Question 6 of 12
What is a lambda expression in modern C++ (C++11+)?
Explanation
Lambdas allow defining inline anonymous function objects, capturing local variables by value [=] or reference [&] for use in algorithms like std::sort.
Question 7 of 12
What mechanism does C++ provide for robust, structured error handling?
Explanation
C++ handles exceptional runtime conditions using 'throw' to raise exceptions, and 'try ... catch' blocks to intercept and handle them gracefully.
Question 8 of 12
What occurs during 'stack unwinding' when an exception is thrown in C++?
Explanation
Stack unwinding guarantees that as frames are exited during exception propagation, local destructors run, ensuring no resource leaks (core of RAII).
Question 9 of 12
What happens if an exception is thrown inside a C++ destructor during stack unwinding?
Explanation
Throwing an exception while another exception is already unwinding invokes std::terminate(). Destructors in C++ should always be 'noexcept'.
Question 10 of 12
What does the algorithm 'std::sort' from <algorithm> require regarding container iterators?
Explanation
std::sort requires random access iterators (O(1) indexing). Containers like std::list provide bidirectional iterators and must use their member list::sort().
Question 11 of 12
What is the standard base class for all standard C++ exception classes in <stdexcept>?
Explanation
All standard library exceptions inherit from 'std::exception', providing a virtual 'what()' method returning an explanatory string.
Question 12 of 12
What does the capture '[&]' specify in a C++ lambda expression?
Explanation
'[&]' is the default capture-by-reference mode, allowing the lambda to read and modify any local variable in the enclosing scope.
0 / 0 (0%)
Great job! Review the questions above and proceed when ready.
ADVERTISEMENT
Responsive Ad Unit • Technical Courses & Certifications